/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package TransmissionPort;

import DbPort.DataReceiver;
import java.io.IOException;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author thang
 */
public class FillSecondaryData {
    
    public static void fill(long gpsData_id, double lat, double lon, int radius) throws IOException{
        DataTypes.GoogleApiDataTypes.Address gpsDataAddress = fillGpsDataAddress(gpsData_id, lat, lon);
        fillPlaces(lat, lon, radius, gpsData_id, gpsDataAddress);
    }
    
    /*
     * from gps data find places (restaurant, bar...) where the user is
     * and stored in db (mysql.places table)
     */
    public static void fillPlaces(double lat, double lon, int radius, long gpsData_id, DataTypes.GoogleApiDataTypes.Address gpsDataAddress) throws IOException{
        int tryTimes = 0;
        DataTypes.GoogleApiDataTypes.PlacesList placesList = null;
        //ResultSet rs = null;
        int mRadius = radius;
        long newPlaceId = 0;
        
        // start search place with the radius is set equal to the accuracy (it is the smallest usable radius)
        while (true){

            tryTimes++;
            
            placesList = GoogleApiPort.GpsDecoder.getPlacesList(lat, lon, mRadius);
            
            // if there is at least one place returned stop query place
            if ( placesList.status.equalsIgnoreCase("OK") ){ break; }
            // if query place more than max times break
            if (tryTimes >= GoogleApiPort.GpsDecoder.MAX_PLACE_QUERY_TIMES){ break; }

            // adjust the radius to get just about 5 places
            // if radius (is set to accuracy) is big -> there to many places returned, can do nothing
            // if the radius is small (higher accuracy) -> there may be no place returned -> increase the radius
            if (placesList.status.equalsIgnoreCase("ZERO_RESULTS")){
                mRadius = mRadius * 2;
            }
        }
        
        // write place list to db
        if ( (placesList != null) && (!(placesList.status.equalsIgnoreCase("ZERO_RESULTS"))) ){
            long mDistance = 0;
            int typeId = 0;
            DataTypes.GoogleApiDataTypes.Address placeAddress = null;
            DataTypes.AutoDiaryDataTypes.Place placePack = new DataTypes.AutoDiaryDataTypes.Place();

            for (DataTypes.GoogleApiDataTypes.Place place : placesList.results) {
                
                // calculate distance to Gps reading posistion
                mDistance = fDistance(lat, lon, place.geometry.location.lat, place.geometry.location.lng);
                // insert into places table
                placePack.setDistance(Math.round(mDistance));
                placePack.setGpsDataId(gpsData_id);
                placePack.setLatitude(place.geometry.location.lat);
                placePack.setLongitude(place.geometry.location.lng);
                placePack.setName(place.name);
                
                newPlaceId = DataReceiver.receiveData(placePack);

                // insert into placesTypes (place types)
                for(String placeType: place.types){
                    typeId = DataReceiver.getDTypesID(placeType.trim());
                    if (typeId < 0){ // not found then insert new one
                        DataTypes.AutoDiaryDataTypes.Type type = new DataTypes.AutoDiaryDataTypes.Type();
                        type.setType(placeType);
                        typeId = DataReceiver.receiveData(type);
                    }

                    // insert into placesTypes
                    DataReceiver.receivePlacesTypes(newPlaceId, typeId);
                }
                
                // get place_address
                placeAddress = GoogleApiPort.GpsDecoder.getPlaceAddress(place.reference);
                if ( matchAddress(placeAddress, gpsDataAddress) ){ // if match update address table
                    DataReceiver.updateAddress(gpsData_id, newPlaceId, "place_id");
                } else{ // insert place address to table
                    DataReceiver.receiveAddress(placeAddress, -1, newPlaceId, -1);
                }
                
                // mactch with gps_data address
                // store place_address if not match
            }
        }
    }

    /*
     * match address of place and gpsData
     * inputs: place address, gpsData_id
     * output: true if place address and gpsData_id's address are the same, false otherwise
     */
    public static boolean matchAddress(DataTypes.GoogleApiDataTypes.Address placeAddress, DataTypes.GoogleApiDataTypes.Address gpsDataAddress){
        boolean result = false;
        
        if( placeAddress == null || gpsDataAddress == null){
            return result;
        }
        
        String[][] placeA = addressToArray(placeAddress);
        String[][] gpsDataA = addressToArray(gpsDataAddress);
        
        // formatted_address match
        // check null
        if( (placeAddress.formatted_address != null) &&  (placeAddress.formatted_address.trim().length() > 0) && (gpsDataAddress.formatted_address != null) && (gpsDataAddress.formatted_address.length() > 0) ){
            if ( placeAddress.formatted_address.trim().equalsIgnoreCase(gpsDataAddress.formatted_address.trim()) ){
                return true;        
            }
        }
        
        //street_number
        boolean bStreet_number = false;
        //check null
        if ( (placeA[1][1] != null) &&  (placeA[1][1].length() > 0) && (gpsDataA[1][1] != null) && (gpsDataA[1][1].length() > 0) ){
            bStreet_number = placeA[1][1].equalsIgnoreCase(gpsDataA[1][1]);
        }

        //route
        boolean bRoute = false;
        if ( (placeA[1][2] != null) &&  (placeA[1][2].length() > 0) && (gpsDataA[1][2] != null) && (gpsDataA[1][2].length() > 0) ){
            /*
            // just compare first word
            String placeWord = placeA[1][2].substring( 0, placeA[1][2].indexOf(" ") ).trim();
            String gpsDataWord = "";

            if ( (gpsDataA[1][2] != null) && gpsDataA[1][2].length() > 0 ) {
                gpsDataWord = gpsDataA[1][2].substring( 0, gpsDataA[1][2].indexOf(" ") ).trim();
            }

            bRoute = placeWord.equalsIgnoreCase(gpsDataWord);
            */
            bRoute = placeA[1][2].equalsIgnoreCase(gpsDataA[1][2]);
        }
        
        //locality
        boolean bLocality = false;
        if ( (placeA[1][3] != null) &&  (placeA[1][3].length() > 0) && (gpsDataA[1][3] != null) && (gpsDataA[1][3].length() > 0) ){
            bLocality = placeA[1][3].equalsIgnoreCase(gpsDataA[1][3]);
        }
        
        //Admin level1
        boolean bAdminL1 = false;
        if ( (placeA[1][4] != null) &&  (placeA[1][4].length() > 0) && (gpsDataA[1][4] != null) && (gpsDataA[1][4].length() > 0) ){
            bAdminL1 = placeA[1][4].equalsIgnoreCase(gpsDataA[1][4]);
        }
        
        //country
        boolean bCountry = false;
        if ( (placeA[1][5] != null) &&  (placeA[1][5].length() > 0) && (gpsDataA[1][5] != null) && (gpsDataA[1][5].length() > 0) ){
            bCountry = placeA[1][5].equalsIgnoreCase(gpsDataA[1][5]);
        }
        
        //postal_code
        boolean bPostal_Code = false;
        if ( (placeA[1][6] != null) &&  (placeA[1][6].length() > 0) && (gpsDataA[1][6] != null) && (gpsDataA[1][6].length() > 0) ){
            bPostal_Code = placeA[1][6].equalsIgnoreCase(gpsDataA[1][6]);
        }
        
        if ( bStreet_number && bRoute && bLocality && bAdminL1 && bCountry ){
            return true;
        }
        if ( bStreet_number && bRoute && bPostal_Code && bCountry ){
            return true;
        }
        
        return result;
    }
    
    public static String[][] addressToArray(DataTypes.GoogleApiDataTypes.Address address){
        
        // in order 
        //  0,1,2,3,4,5,6
        //formatted_address 0, street_number 1, route 2, locality 3, administrative_area_level_1 4, country 5, postal_code 6
        String[][] result = new String[2][7];
        
        // init array
        for( int i=0; i < result.length; i++ ){
            for( int j=0; j<result[i].length; j++ ){
                result[i][j] = "";
            }
        }
        
        result[0][0] = "formatted_address";
        result[1][0] = address.formatted_address;
        
        DataTypes.GoogleApiDataTypes.Address_components address_component = null;
        
        for ( Iterator<DataTypes.GoogleApiDataTypes.Address_components> it = address.address_components.iterator(); it.hasNext(); ){
            address_component = it.next();
            
            // check type of the component
            for ( String type : address_component.types){
                // if have street number or street address
                if ( type.equalsIgnoreCase(DataTypes.GoogleApiDataTypes.Address_Component_Types.street_number.toString()) || 
                        type.equalsIgnoreCase(DataTypes.GoogleApiDataTypes.Address_Component_Types.street_address.toString()) ){
                    result[0][1] = DataTypes.GoogleApiDataTypes.Address_Component_Types.street_number.toString();
                    result[1][1] = address_component.short_name.trim();
                    break;
                }
                
                // if have route
                if ( type.equalsIgnoreCase(DataTypes.GoogleApiDataTypes.Address_Component_Types.route.toString()) ){
                    result[0][2] = DataTypes.GoogleApiDataTypes.Address_Component_Types.route.toString();
                    result[1][2] = address_component.short_name.trim();
                    break;
                }

                // if have locality
                if ( type.equalsIgnoreCase(DataTypes.GoogleApiDataTypes.Address_Component_Types.locality.toString()) ){
                    result[0][3] = DataTypes.GoogleApiDataTypes.Address_Component_Types.locality.toString();
                    result[1][3] = address_component.short_name.trim();
                    break;
                }

                // if have administrative_area_level_1
                if ( type.equalsIgnoreCase(DataTypes.GoogleApiDataTypes.Address_Component_Types.administrative_area_level_1.toString()) ){
                    result[0][4] = DataTypes.GoogleApiDataTypes.Address_Component_Types.administrative_area_level_1.toString();
                    result[1][4] = address_component.short_name.trim();
                    break;
                }

                // if have country
                if ( type.equalsIgnoreCase(DataTypes.GoogleApiDataTypes.Address_Component_Types.country.toString()) ){
                    result[0][5] = DataTypes.GoogleApiDataTypes.Address_Component_Types.country.toString();
                    result[1][5] = address_component.short_name.trim();
                    break;
                }
                
                // if have postal_code
                if ( type.equalsIgnoreCase(DataTypes.GoogleApiDataTypes.Address_Component_Types.postal_code.toString()) ){
                    result[0][6] = DataTypes.GoogleApiDataTypes.Address_Component_Types.postal_code.toString();
                    result[1][6] = address_component.short_name.trim();
                    break;
                }
            }
        }
        
        return result;
    }
    
    /*
     * calculate distace between two points specified by lat and long
     * ref: using haversine formula from: http://www.movable-type.co.uk/scripts/latlong.html
     * return: distance in m
     */
    public static long fDistance(double lat1, double lng1, double lat2, double lng2){
        int R = 6371;   //km
        double dLat = Math.toRadians(lat2 - lat1);
        double dLng = Math.toRadians(lng2 - lng1);
        //double dLat = (lat2 - lat1) * (Math.PI / 180);
        //double dLng = (lng2 - lng1) * (Math.PI / 180);
        double rLat1 = Math.toRadians(lat1);
        double rLat2 = Math.toRadians(lat2);
        
        double a = Math.pow(Math.sin(dLat/2),2) + Math.cos(rLat1) * Math.cos(rLat2) * Math.pow(Math.sin(dLng/2), 2);
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        double d = R * c;
        
        return Math.round(d*1000);
    }


    public static void fillGpsDataAddress(long gpsData_id){}
    
    // can get latitude and longitude from db but use these imputs to reduces DB 
    // return the address just got
    // if error return null
    public static DataTypes.GoogleApiDataTypes.Address fillGpsDataAddress(long gpsData_id, double gpsLat, double gpsLong){
        DataTypes.GoogleApiDataTypes.Address gpsDataAddress = null;
        
        try {
            // get gps_address and insert gps_address into table
            gpsDataAddress = GoogleApiPort.GpsDecoder.getGpsDataAddress(gpsLat, gpsLong);
            
            long distanceL = fDistance(gpsLat, gpsLong, gpsDataAddress.geometry.location.lat, gpsDataAddress.geometry.location.lng);
            int distance = Math.round(distanceL);
            
            DataReceiver.receiveAddress(gpsDataAddress, gpsData_id, -1, distance);
            
        } catch (IOException ex) {
            Logger.getLogger(FillSecondaryData.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        return gpsDataAddress;
    }

    
}
